package npr;

import processing.core.*;

import javax.media.opengl.*;
import com.sun.opengl.util.*;

import java.nio.ShortBuffer;

/**
 * Informally, suggestive contours are almost contours, or contours in nearby viewpoints.
 * The image space algorithm works by rendering the dot product between 
 * normals and view vector to a texture by placing a light at the camera position
 * and then running a filter that detects steep valleys in intensity.
 */
public class SuggestiveContours extends ImageSpaceLines{

    /**
     * Shader for coding the dot product between normals and view vector
     * by placing a light at the camera position.
     */
    private GLSL suggestiveContourShader;
    /** 
     * Shader for finding suggestive contours without any additional rendering passes. 
     * The radius of the circle-shaped kernel size is 1.
     * Line strength is coded as alpha. 
     * This is in addition to the filter for the general case, because some older 
     * graphics cards have problems with larger filter kernels.
     */
    private GLSL findSuggestiveContoursToScreen3x3;
    /**
     * Shader for  finding suggestive contours with additional rendering passes. 
     * The radius of the circle-shaped kernel size is 1.
     * Line strength is coded in the color channel, as reading back the 
     * alpha values in GLSL didn't work out. :( 
     * This is in addition to the filter for the general case, because some older 
     * graphics cards have problems with larger filter kernels.
     */
    private GLSL findSuggestiveContoursToTexture3x3;
    /** 
     * Shader for finding suggestive contours without any additional rendering passes. 
     * The radius of the circle-shaped kernel size is 2.
     * Line strength is coded as alpha. 
     * This is in addition to the filter for the general case, because some older 
     * graphics cards have problems with larger filter kernels.
     */
    private GLSL findSuggestiveContoursToScreen5x5;
    /**
     * Shader for  finding suggestive contours with additional rendering passes. 
     * The radius of the circle-shaped kernel size is 2.
     * Line strength is coded in the color channel, as reading back the 
     * alpha values in GLSL didn't work out. :( 
     * This is in addition to the filter for the general case, because some older 
     * graphics cards have problems with larger filter kernels.
     */
    private GLSL findSuggestiveContoursToTexture5x5;
    /**
     * Shader for  finding suggestive contours for radius other than 1 or 2
     * without any additional rendering passes.
     * Line strength is coded as alpha.
     */
    private GLSL findSuggestiveContoursToScreen;
    /**
     * Shader for  finding suggestive contours for radius other than 1 or 2
     * with additional rendering passes.
     * Line strength is coded in the color channel, as reading back the 
     * alpha values in GLSL didn't work out. :( 
     */
    private GLSL findSuggestiveContoursToTexture;
    /** 
     * Shader for median filtering. The filter kernel size is 3x3.
     * Line strength is coded as alpha. 
     * This is in addition to the filter for the general case, because some older 
     * graphics cards have problems with larger filter kernels.
     */
    private GLSL median3x3;
    /** 
     * Shader for median filtering. The filter kernel size is 5x5.
     * Line strength is coded as alpha. 
     * This is in addition to the filter for the general case, because some older 
     * graphics cards have problems with larger filter kernels.
     */
    private GLSL median5x5;
    /**
     * Currently not implemented
     */
    private GLSL median;

    /**
     * Texture holding the result of rendering the dot product between 
     * normals and view vector to a texture by placing a light at the camera position.
     */
    private RenderToTexture suggestiveContourTexture;
    /**
     * Texture holding the found suggestive contours for subsequent median filtering.
     */
    private RenderToTexture foundSuggestiveContours;

    /**
     * The start value for smoothstep interpolation.
     */
    protected float smoothstepStart= 0.0f;
    /**
     * The end value for smoothstep interpolation.
     */
    protected float smoothstepEnd= 1.0f;
    /**
     * Specifies whether to run median filter.
     */
    private boolean useMedian= false;
    
    /**
     * Specifies whether to use smoothstep interpolation.
     */
    protected boolean useSmoothstep= false;

    /**
     * Creates and initializes a SuggestiveContours object.
     */
    public SuggestiveContours(){

	suggestiveContourTexture= new RenderToTexture();
	foundSuggestiveContours= new RenderToTexture();

	suggestiveContourShader=new GLSL();
        suggestiveContourShader.loadVertexShader("suggestiveContourShader.vert");
        suggestiveContourShader.loadFragmentShader("suggestiveContourShader.frag");
	suggestiveContourShader.useShaders();	

	
	findSuggestiveContoursToScreen3x3=new GLSL();
        findSuggestiveContoursToScreen3x3.loadVertexShader("findSuggestiveContours.vert");
        findSuggestiveContoursToScreen3x3.loadFragmentShader("findSuggestiveContoursToScreen3x3.frag");
	findSuggestiveContoursToScreen3x3.useShaders();


	findSuggestiveContoursToTexture3x3=new GLSL();
	findSuggestiveContoursToTexture3x3.loadVertexShader("findSuggestiveContours.vert");
	findSuggestiveContoursToTexture3x3.loadFragmentShader("findSuggestiveContoursToTexture3x3.frag");
	findSuggestiveContoursToTexture3x3.useShaders();


	findSuggestiveContoursToScreen5x5=new GLSL();
        findSuggestiveContoursToScreen5x5.loadVertexShader("findSuggestiveContours.vert");
        findSuggestiveContoursToScreen5x5.loadFragmentShader("findSuggestiveContoursToScreen5x5.frag");
	findSuggestiveContoursToScreen5x5.useShaders();


	findSuggestiveContoursToTexture5x5=new GLSL();
	findSuggestiveContoursToTexture5x5.loadVertexShader("findSuggestiveContours.vert");
	findSuggestiveContoursToTexture5x5.loadFragmentShader("findSuggestiveContoursToTexture5x5.frag");
	findSuggestiveContoursToTexture5x5.useShaders();

	findSuggestiveContoursToScreen= new GLSL();
        findSuggestiveContoursToScreen.loadVertexShader("findSuggestiveContours.vert");
        findSuggestiveContoursToScreen.loadFragmentShader("findSuggestiveContoursToScreen.frag");
        findSuggestiveContoursToScreen.useShaders();

	findSuggestiveContoursToTexture= new GLSL();
        findSuggestiveContoursToTexture.loadVertexShader("findSuggestiveContours.vert");
        findSuggestiveContoursToTexture.loadFragmentShader("findSuggestiveContoursToTexture.frag");
        findSuggestiveContoursToTexture.useShaders();

	
	median3x3=new GLSL();
        median3x3.loadVertexShader("median.vert");
        median3x3.loadFragmentShader("median3x3.frag");
        median3x3.useShaders();
	median5x5=new GLSL();
        median5x5.loadVertexShader("median.vert");
        median5x5.loadFragmentShader("median5x5.frag");
        median5x5.useShaders();
	median=new GLSL();
        median.loadVertexShader("median.vert");
        median.loadFragmentShader("median.frag");
        median.useShaders();
	
    }

    /**
     * Sets the radius of the circle-shaped filter mask.
     *
     * @param width the radius. Will be casted to an integer.
     */
    public void setWidth(float width){
	
	this.width= width;
    }


    /**
     * Specifies whether to use smoothstep interpolation.
     * 
     * @param useSmoothstep true if smoothstep should be used, false if not.
     * The default is false.
     * 
     * @see npr.SuggestiveContours#setSmoothstepStart
     * @see snpr.SuggestiveContours#setSmoothstepEnd
     */
    public void useSmoothstep(boolean useSmoothstep){

	this.useSmoothstep= useSmoothstep;
    }


    /**
     * Sets the start value for smoothstep interpolation.
     *
     * @param start the start value for smoothstep interpolation.
     * 
     * @see npr.SuggestiveContours#useSmoothstep
     * @see npr.SuggestiveContours#setSmoothstepEnd
     */
    public void setSmoothstepStart(float start){

	this.smoothstepStart= start;
    }
 

   /**
     * Sets the end value for smoothstep interpolation.
     *
     * @param end the end value for smoothstep interpolation.
     *
     * @see npr.SuggestiveContours#useSmoothstep
     * @see npr.SuggestiveContours#setSmoothstepStart
     */
    public void setSmoothstepEnd(float end){

	this.smoothstepEnd= end;
    }
    
    /**
     * Specifies whether a median filter should be run after detecting
     * suggestive contours.
     *
     * @param useMedian true if median is to be used, false if not.
     * The default is false.
     */
    public void useMedian(boolean useMedian){
	this.useMedian= useMedian;
    }
   
    /** 
     * Renders the dot product between normals and view vector to a texture 
     * by placing a light at the camera position.
     * Also renders suggestive contours to a texture, in case a median filter
     * is to be run afterwards.
     * You don't have to call this medhod. It is called by the renderer.
     */
    public void preProcess(){

	
	renderer= (NPR)parent.g;
	gl= renderer.gl;
	
	gl.glEnable(GL.GL_TEXTURE_2D);
    
	// render scene with diffuse light at camera position

	gl.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
	suggestiveContourTexture.begin();
	    
	suggestiveContourShader.startShader();

	int location= suggestiveContourShader.getUniformLocation("cameraPos");
	suggestiveContourShader.setFloatVec3(location, renderer.eyeX, renderer.eyeY, renderer.eyeZ);

	//renderer.fullScreenQuad(1.0f);
	render();

	suggestiveContourShader.endShader();
	    
	suggestiveContourTexture.end();
    	
	if(useMedian){ 
	    foundSuggestiveContours.begin();
	    findSuggestiveContours(true);
	    foundSuggestiveContours.end();	    
	}   

	gl.glDisable(GL.GL_TEXTURE_2D);
    }

    /**
     * Either finds suggestive contours or runs a median filter.
     * You don't have to call this medhod. It is called by the renderer.
     */
    public void postProcess(){

	renderer= (NPR)parent.g;
	gl= renderer.gl;
	
	gl.glEnable(GL.GL_DEPTH_TEST);
	gl.glDepthFunc(GL.GL_LEQUAL);

	if(useMedian){
	    findMedian();
	}
	else{
	    findSuggestiveContours(false);
	}
    }

    /**
     * Finds suggestive contours.
     */
    protected void findSuggestiveContours(boolean toTexture){
    
	renderer= (NPR)parent.g;
	gl= renderer.gl;

	int location;

	if((int)width==1){

	    if(toTexture){
		findSuggestiveContoursToTexture3x3.startShader();

		gl.glActiveTexture(GL.GL_TEXTURE0);
		suggestiveContourTexture.bindDepthTexture();
		location= findSuggestiveContoursToTexture3x3.getUniformLocation("depthTexture");
		findSuggestiveContoursToTexture3x3.setTextureUnit(location, 0);
		
		gl.glActiveTexture(GL.GL_TEXTURE1);
		suggestiveContourTexture.bindColorTexture();
		location= findSuggestiveContoursToTexture3x3.getUniformLocation("suggestiveContourTexture");
		findSuggestiveContoursToTexture3x3.setTextureUnit(location, 1);
		
		location= findSuggestiveContoursToTexture3x3.getUniformLocation("smoothstepStart");
		findSuggestiveContoursToTexture3x3.setFloat(location, smoothstepStart);
		
		location= findSuggestiveContoursToTexture3x3.getUniformLocation("useSmoothstep");
		findSuggestiveContoursToTexture3x3.setBoolean(location, useSmoothstep);
		
		location= findSuggestiveContoursToTexture3x3.getUniformLocation("smoothstepEnd");
		findSuggestiveContoursToTexture3x3.setFloat(location, smoothstepEnd);
	        
		location= findSuggestiveContoursToTexture3x3.getUniformLocation("screenSize");
		findSuggestiveContoursToTexture3x3.setFloatVec2(location, suggestiveContourTexture.getWidth(), suggestiveContourTexture.getHeight());
		
		location= findSuggestiveContoursToTexture3x3.getUniformLocation("color");
		findSuggestiveContoursToTexture3x3.setFloatVec4(location, red, green, blue, alpha);
		
		render();
		//render a full-screen quad to cover all pixels
		renderer.fullScreenQuad(1.0f);
		
		findSuggestiveContoursToTexture3x3.endShader();
	    }
	    else{
		findSuggestiveContoursToScreen3x3.startShader();
		
		gl.glActiveTexture(GL.GL_TEXTURE0);
		suggestiveContourTexture.bindDepthTexture();
		location= findSuggestiveContoursToScreen3x3.getUniformLocation("depthTexture");
		findSuggestiveContoursToScreen3x3.setTextureUnit(location, 0);
		
		gl.glActiveTexture(GL.GL_TEXTURE1);
		suggestiveContourTexture.bindColorTexture();
		location= findSuggestiveContoursToScreen3x3.getUniformLocation("suggestiveContourTexture");
		findSuggestiveContoursToScreen3x3.setTextureUnit(location, 1);
		
		location= findSuggestiveContoursToScreen3x3.getUniformLocation("smoothstepStart");
		findSuggestiveContoursToScreen3x3.setFloat(location, smoothstepStart);
		
		location= findSuggestiveContoursToScreen3x3.getUniformLocation("useSmoothstep");
		findSuggestiveContoursToScreen3x3.setBoolean(location, useSmoothstep);
		
		location= findSuggestiveContoursToScreen3x3.getUniformLocation("smoothstepEnd");
		findSuggestiveContoursToScreen3x3.setFloat(location, smoothstepEnd);
	        
		location= findSuggestiveContoursToScreen3x3.getUniformLocation("screenSize");
		findSuggestiveContoursToScreen3x3.setFloatVec2(location, parent.width, parent.height);
		
		location= findSuggestiveContoursToScreen3x3.getUniformLocation("color");
		findSuggestiveContoursToScreen3x3.setFloatVec4(location, red, green, blue, alpha);
		
		render();
		//render a full-screen quad to cover all pixels
		renderer.fullScreenQuad(1.0f);
		
		findSuggestiveContoursToScreen3x3.endShader();
	    }
	}
	else if((int)width==2){
	
	    if(toTexture){
		findSuggestiveContoursToTexture5x5.startShader();

		gl.glActiveTexture(GL.GL_TEXTURE0);
		suggestiveContourTexture.bindDepthTexture();
		location= findSuggestiveContoursToTexture5x5.getUniformLocation("depthTexture");
		findSuggestiveContoursToTexture5x5.setTextureUnit(location, 0);
		
		gl.glActiveTexture(GL.GL_TEXTURE1);
		suggestiveContourTexture.bindColorTexture();
		location= findSuggestiveContoursToTexture5x5.getUniformLocation("suggestiveContourTexture");
		findSuggestiveContoursToTexture5x5.setTextureUnit(location, 1);
		
		location= findSuggestiveContoursToTexture5x5.getUniformLocation("smoothstepStart");
		findSuggestiveContoursToTexture5x5.setFloat(location, smoothstepStart);
		
		location= findSuggestiveContoursToTexture5x5.getUniformLocation("useSmoothstep");
		findSuggestiveContoursToTexture5x5.setBoolean(location, useSmoothstep);
		
		location= findSuggestiveContoursToTexture5x5.getUniformLocation("smoothstepEnd");
		findSuggestiveContoursToTexture5x5.setFloat(location, smoothstepEnd);
	        
		location= findSuggestiveContoursToTexture5x5.getUniformLocation("screenSize");
		findSuggestiveContoursToTexture3x3.setFloatVec2(location, suggestiveContourTexture.getWidth(), suggestiveContourTexture.getHeight());
		
		location= findSuggestiveContoursToTexture5x5.getUniformLocation("color");
		findSuggestiveContoursToTexture5x5.setFloatVec4(location, red, green, blue, alpha);
		
		render();
		//render a full-screen quad to cover all pixels
		renderer.fullScreenQuad(1.0f);
		
		findSuggestiveContoursToTexture5x5.endShader();
	    }
	    else{
		findSuggestiveContoursToScreen5x5.startShader();
		
		gl.glActiveTexture(GL.GL_TEXTURE0);
		suggestiveContourTexture.bindDepthTexture();
		location= findSuggestiveContoursToScreen5x5.getUniformLocation("depthTexture");
		findSuggestiveContoursToScreen5x5.setTextureUnit(location, 0);
		
		gl.glActiveTexture(GL.GL_TEXTURE1);
		suggestiveContourTexture.bindColorTexture();
		location= findSuggestiveContoursToScreen5x5.getUniformLocation("suggestiveContourTexture");
		findSuggestiveContoursToScreen5x5.setTextureUnit(location, 1);
		
		location= findSuggestiveContoursToScreen5x5.getUniformLocation("smoothstepStart");
		findSuggestiveContoursToScreen5x5.setFloat(location, smoothstepStart);
		
		location= findSuggestiveContoursToScreen5x5.getUniformLocation("useSmoothstep");
		findSuggestiveContoursToScreen5x5.setBoolean(location, useSmoothstep);
		
		location= findSuggestiveContoursToScreen5x5.getUniformLocation("smoothstepEnd");
		findSuggestiveContoursToScreen5x5.setFloat(location, smoothstepEnd);
	        
		location= findSuggestiveContoursToScreen5x5.getUniformLocation("screenSize");
		findSuggestiveContoursToScreen5x5.setFloatVec2(location, parent.width, parent.height);
		
		location= findSuggestiveContoursToScreen5x5.getUniformLocation("color");
		findSuggestiveContoursToScreen5x5.setFloatVec4(location, red, green, blue, alpha);
		
		render();
		//render a full-screen quad to cover all pixels
		renderer.fullScreenQuad(1.0f);
		
		findSuggestiveContoursToScreen5x5.endShader();
	    }
	}
	else{

	    if(toTexture){
		findSuggestiveContoursToTexture.startShader();
		
		gl.glActiveTexture(GL.GL_TEXTURE0);
		suggestiveContourTexture.bindDepthTexture();
		location= findSuggestiveContoursToTexture.getUniformLocation("depthTexture");
		findSuggestiveContoursToTexture.setTextureUnit(location, 0);
	    
		gl.glActiveTexture(GL.GL_TEXTURE1);
		suggestiveContourTexture.bindColorTexture();
		location= findSuggestiveContoursToTexture.getUniformLocation("suggestiveContourTexture");
		findSuggestiveContoursToTexture.setTextureUnit(location, 1);
	
		location= findSuggestiveContoursToTexture.getUniformLocation("radius");
		findSuggestiveContoursToTexture.setFloat(location, width);
	
		location= findSuggestiveContoursToTexture.getUniformLocation("useSmoothstep");
		findSuggestiveContoursToTexture.setBoolean(location, useSmoothstep);

		location= findSuggestiveContoursToTexture.getUniformLocation("smoothstepStart");
		findSuggestiveContoursToTexture.setFloat(location, smoothstepStart);
	
		location= findSuggestiveContoursToTexture.getUniformLocation("smoothstepEnd");
		findSuggestiveContoursToTexture.setFloat(location, smoothstepEnd);

		location= findSuggestiveContoursToTexture.getUniformLocation("screenSize");
		findSuggestiveContoursToTexture.setFloatVec2(location, suggestiveContourTexture.getWidth(), suggestiveContourTexture.getHeight());
	
		location= findSuggestiveContoursToTexture.getUniformLocation("color");
		findSuggestiveContoursToTexture.setFloatVec4(location, red, green, blue, alpha);
		render();
		//render a full-screen quad to cover all pixels
		renderer.fullScreenQuad(1.0f);
	
		findSuggestiveContoursToTexture.endShader();
	    }
	    else{
		findSuggestiveContoursToScreen.startShader();
		
		gl.glActiveTexture(GL.GL_TEXTURE0);
		suggestiveContourTexture.bindDepthTexture();
		location= findSuggestiveContoursToScreen.getUniformLocation("depthTexture");
		findSuggestiveContoursToScreen.setTextureUnit(location, 0);
	    
		gl.glActiveTexture(GL.GL_TEXTURE1);
		suggestiveContourTexture.bindColorTexture();
		location= findSuggestiveContoursToScreen.getUniformLocation("suggestiveContourTexture");
		findSuggestiveContoursToScreen.setTextureUnit(location, 1);
	
		location= findSuggestiveContoursToScreen.getUniformLocation("radius");
		findSuggestiveContoursToScreen.setFloat(location, width);
	
		location= findSuggestiveContoursToScreen.getUniformLocation("useSmoothstep");
		findSuggestiveContoursToScreen.setBoolean(location, useSmoothstep);

		location= findSuggestiveContoursToScreen.getUniformLocation("smoothstepStart");
		findSuggestiveContoursToScreen.setFloat(location, smoothstepStart);
	
		location= findSuggestiveContoursToScreen.getUniformLocation("smoothstepEnd");
		findSuggestiveContoursToScreen.setFloat(location, smoothstepEnd);

		location= findSuggestiveContoursToScreen.getUniformLocation("screenSize");
		findSuggestiveContoursToScreen.setFloatVec2(location, parent.width, parent.height);
	
		location= findSuggestiveContoursToScreen.getUniformLocation("color");
		findSuggestiveContoursToScreen.setFloatVec4(location, red, green, blue, alpha);
		render();
		//render a full-screen quad to cover all pixels
		renderer.fullScreenQuad(1.0f);
	
		findSuggestiveContoursToScreen.endShader();
	    }
	}
		
	gl.glActiveTexture(GL.GL_TEXTURE1);
	suggestiveContourTexture.unbind();	
	gl.glActiveTexture(GL.GL_TEXTURE0);
	suggestiveContourTexture.unbind();		
    }

    /**
     * Median filtering
     */
    protected void findMedian(){
    
	renderer= (NPR)parent.g;
	gl= renderer.gl; 

	int location;

	if((int)width==1){
 
	    median3x3.startShader();
		
	    gl.glActiveTexture(GL.GL_TEXTURE0);
	    suggestiveContourTexture.bindDepthTexture();
	    location= median3x3.getUniformLocation("depthTexture");
	    median3x3.setTextureUnit(location, 0);
	    
	    gl.glActiveTexture(GL.GL_TEXTURE1);
	    foundSuggestiveContours.bindColorTexture();
	    location= median3x3.getUniformLocation("suggestiveContours");
	    median3x3.setTextureUnit(location, 1);

	    location= median3x3.getUniformLocation("screenSize");
	    median3x3.setFloatVec2(location, parent.width, parent.height);

	    location= median3x3.getUniformLocation("color");
	    median3x3.setFloatVec4(location, red, green, blue, alpha);
	
	    render();
	    //render a full-screen quad to cover all pixels
	    renderer.fullScreenQuad(1.0f);
	
	    median3x3.endShader();
	}
	else if((int)width==2){

	    median5x5.startShader();
	
	    gl.glActiveTexture(GL.GL_TEXTURE0);
	    suggestiveContourTexture.bindDepthTexture();
	    location= median5x5.getUniformLocation("depthTexture");
	    median5x5.setTextureUnit(location, 0);
	    
	    gl.glActiveTexture(GL.GL_TEXTURE1);
	    foundSuggestiveContours.bindColorTexture();
	    location= median5x5.getUniformLocation("suggestiveContours");
	    median5x5.setTextureUnit(location, 1);
	
	    location= median5x5.getUniformLocation("screenSize");
	    median5x5.setFloatVec2(location, parent.width, parent.height);
	
	    location= median5x5.getUniformLocation("color");
	    median5x5.setFloatVec4(location, red, green, blue, alpha);
	
	    render();
	    //render a full-screen quad to cover all pixels
	    renderer.fullScreenQuad(1.0f);
	
	    median5x5.endShader();	
	
	}
	else{

	    median.startShader();

	    gl.glActiveTexture(GL.GL_TEXTURE0);
	    suggestiveContourTexture.bindDepthTexture();
	    location= median.getUniformLocation("depthTexture");
	    median.setTextureUnit(location, 0);
	    
	    gl.glActiveTexture(GL.GL_TEXTURE1);
	    foundSuggestiveContours.bindColorTexture();
	    location= median.getUniformLocation("suggestiveContours");
	    median.setTextureUnit(location, 1);
	
	    location= median.getUniformLocation("screenSize");
	    median.setFloatVec2(location, parent.width, parent.height);
	
	    location= median.getUniformLocation("color");
	    median.setFloatVec4(location, red, green, blue, alpha);
	
	    render();
	    //render a full-screen quad to cover all pixels
	    renderer.fullScreenQuad(1.0f);
	
	    median.endShader();
	}
	
	gl.glActiveTexture(GL.GL_TEXTURE1);
        foundSuggestiveContours.unbind();
	gl.glActiveTexture(GL.GL_TEXTURE0);
	suggestiveContourTexture.unbind();
    }
}